#include "math.hpp"
#include "utils.hpp"
#include "builtins.hpp"

#include <cmath>
#include <cstdlib>
#include <stack>

Variable _bi_math_solve(Variable a, Variable op, Variable b, bool allowEq)
{
	Variable ret = 0;
	if(op.str=="+") {
		if(a.type == tNumeric && b.type == tNumeric) {
			ret = a.i + b.i;
		} else if(a.type == tArray) {
			if(b.type == tArray) {
				a.arr = vectorconcat(a.arr,b.arr);
			} else {
				a.arr.push_back(b);
			}
			ret = a;
		} else {
			ret = a.str + b.str;
		}
	} else if(op.str=="-") {
		ret = a.i - b.i;
	} else if(op.str=="*") {
		ret = a.i * b.i;
	} else if(op.str=="/") {
		if(b.i!=0) {
			ret = a.i / b.i;
		} else {
			ret = a.i;
		}
	} else if(op.str=="%") {
		ret = fmod(a.i,b.i);
	} else if(op.str=="**") {
		ret = pow(a.i,b.i);
	} else if(op.str=="<<") {
		ret = a.i * pow(2,b.i);
	} else if(op.str==">>") {
		ret = a.i * pow(2,-b.i);
	} else if(op.str=="|") {
		ret = (int)a.i | (int)b.i;
	} else if(op.str=="&") {
		ret = (int)a.i & (int)b.i;
	} else if(op.str=="^") {
		ret = (int)a.i ^ (int)b.i;
	} else {
		if(allowEq) {
			if(op.str=="=") {
				ret = b;
			} else if(op.str=="+=") {
				if(a.type == tNumeric && b.type == tNumeric) {
					ret = a.i + b.i;
				} else if(a.type == tArray) {
					if(b.type == tArray) {
						a.arr = vectorconcat(a.arr,b.arr);
					} else {
						a.arr.push_back(b);
					}
					ret = a;
				} else {
					ret = a.str + b.str;
				}
			} else if(op.str=="-=") {
				ret = a.i - b.i;
			} else if(op.str=="*=") {
				ret = a.i * b.i;
			} else if(op.str=="/=") {
				if(b.i!=0) {
					ret = a.i / b.i;
				} else {
					ret = a.i;
				}
			} else if(op.str=="%=") {
				ret = fmod(a.i,b.i);
			} else if(op.str=="**=") {
				ret = pow(a.i,b.i);
			} else if(op.str=="<<=") {
				ret = (int)a.i << (int)b.i;
			} else if(op.str==">>=") {
				ret = (int)a.i >> (int)b.i;
			} else if(op.str=="|=") {
				ret = (int)a.i | (int)b.i;
			} else if(op.str=="&=") {
				ret = (int)a.i & (int)b.i;
			} else if(op.str=="^=") {
				ret = (int)a.i ^ (int)b.i;
			} else {
				if(a.type == tString || b.type == tString) {
					ret = checkconditional(a.str,op.str,b.str);
				} else {
					ret = checkconditional(a.i,op.str,b.i);
				}
			}
		} else {
			if(a.type == tString || b.type == tString) {
				ret = checkconditional(a.str,op.str,b.str);
			} else {
				ret = checkconditional(a.i,op.str,b.i);
			}
		}
	}
	return ret;
}

int Operator_Priority(string str)
{
	// Based on the C++ order of operations
	if(str == "**") {
		return 6;
	} else if(str == "*" || str == "/" || str == "%") {
		return 5;
	} else if(str == "+" || str == "-") {
		return 4;
	} else if(str == "<<" || str == ">>") {
		return 3;
	} else if(str == "==" || str == "!=" || str == ">" || str == ">=" || str == "<" || str == "<=") {
		return 2;
	} else if(str == "|" || str == "&" || str == "^") {
		return 1;
	} else {
		return 0;
	}
}

Variable bi_math(vector<Variable>& argv, Environment& env)
{
	Variable tmp;
	// Preprocessing to make sure the {2 -3} bug isn't in here
	for(unsigned i=1;i<argv.size();i++) {
		tmp = argv[i];
		env.verify(tmp);
		if(tmp.type == tNumeric && i<(argv.size()-1)) {
			tmp = argv[++i];
			env.verify(tmp);
			if(tmp.type == tNumeric) {
				Variable op = (Variable)"+";
				op.type = tOpcode;
				argv.insert(argv.begin()+i,op);
			}
		}
	}
	
	// Step 1: infix to postfix
	stack<Variable> s;
	vector<Variable> postfix;
	
	for(unsigned i=1;i<argv.size();i++) {
		env.verify(argv[i]);
		switch(argv[i].type) {
		case tOpcode: {
			while(s.size()) {
				int new_prio = Operator_Priority(argv[i].str);
				int old_prio = Operator_Priority(s.top().str);
				if(new_prio > old_prio) {
					// New operator gets pushed directly onto the stack
					break;
				} else {
					// Pop anything off and put it into the postfix list
					postfix.push_back(s.top());
					s.pop();
				}
			}
			s.push(argv[i]);
			break;
		}
		case tNumeric:
		case tString: {
			postfix.push_back(argv[i]);
			break;
		}
		default: {
			throw ERROR_ARGUMENTS;
			break;
		}
		};
	}
	while(s.size()) {
		postfix.push_back(s.top());
		s.pop();
	}
	
	// s is now empty, so we'll reuse it
	
	// Step 2: solve the postfix expression
	for(unsigned i=0;i<postfix.size();i++) {
		switch(postfix[i].type) {
		case tOpcode: {
			//Pop two and do the matching operation
			// They're done backwards...just part of the algorithm
			Variable a, b;
			b = s.top();
			s.pop();
			a = s.top();
			s.pop();
			s.push(_bi_math_solve(a,postfix[i],b));
			break;
		}
		case tNumeric:
		case tString: {
			s.push(postfix[i]);
			break;
		}
		default: {
			// It should never reach this, but just in case...
			throw ERROR_ARGUMENTS;
			break;
		}
		};
	}
	
	return s.top();
}

Variable bi_sin(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &a = argv[1];
	env.verify(a);
	if(env.getvar("__degrees__").i==0) {
		ret = sin(a.i);
	} else {
		ret = sin(a.i * PI / 180);
	}
	return ret;
}

Variable bi_cos(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &a = argv[1];
	env.verify(a);
	if(env.getvar("__degrees__").i==0) {
		ret = cos(a.i);
	} else {
		ret = cos(a.i * PI / 180);
	}
	return ret;
}

Variable bi_tan(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &a = argv[1];
	env.verify(a);
	if(env.getvar("__degrees__").i==0) {
		ret = tan(a.i);
	} else {
		ret = tan(a.i * PI / 180);
	}
	return ret;
}

Variable bi_asin(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &a = argv[1];
	env.verify(a);
	ret = asin(a.i);
	if(env.getvar("__degrees__").i==1) {
		ret = ret.i * 180 / PI;
	}
	return ret;
}

Variable bi_acos(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &a = argv[1];
	env.verify(a);
	ret = acos(a.i);
	if(env.getvar("__degrees__").i==1) {
		ret = ret.i * 180 / PI;
	}
	return ret;
}

Variable bi_atan(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc!=2) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable &a = argv[1];
	env.verify(a);
	ret=atan(a.i);
	if(env.getvar("__degrees__").i==1) {
		ret = ret.i * 180 / PI;
	}
	return ret;
}

Variable bi_rand(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if( (argc>3) || (argc<2) ) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	double min, max;
	Variable &a = argv[1], b;
	env.verify(a);
	if(argc == 2) {
		min = 0;
		max = a.i;
	} else if(argc == 3) {
		b = argv[2];
		env.verify(b);
		max=b.i;
		min=a.i;
	}
	ret = min + fmod(rand(), (max-min) );
	return ret;
}

Variable bi_frand(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if( (argc>3) || (argc<2) ) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	double min, max;
	Variable &a = argv[1], b;
	env.verify(a);
	if(argc == 2) {
		min = 0;
		max = a.i;
	} else if(argc == 3) {
		b = argv[2];
		env.verify(b);
		max=b.i;
		min=a.i;
	}
	ret = ((max-min)*((double)rand()/RAND_MAX))+min;
	return ret;
}

Variable bi_log(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if( (argc > 3) || (argc < 2) ) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable base,num;
	if(argc == 3) {
		num = argv[2];
		env.verify(num);
		base = argv[1];
		env.verify(base);
	} else {
		base = 10;
		num = argv[1];
		env.verify(num);
	}
	ret = log10(num.i)/log10(base.i);
	return ret;
}
